#include "Utils.h"

#include <iostream>
#include <fstream>
#include <dirent.h>
#include <cstring>
#include <json/json.h>
#include <opencv2/opencv.hpp>

namespace Utils
{

bool using_img = false;
std::string ext_names = ".mp4 .jpg";
int last_img = 0;
int video_speed = 5;
int max_find_armor_red = 5;
int max_find_armor_blue = 5;
bool save_error_img_only = false;

template<typename T>
void merge_vector(std::vector<T> &dst, const std::vector<T> &src)
{
    dst.reserve(src.size());
    for (auto &j: src)
    {
        dst.template emplace_back(j);
    }
}

static std::string join_path(const std::string &p1, const std::string &p2)
{
    std::string joined_path;
    if (p1.back() == '/' && p2.front() == '/')
    {
        joined_path = p1 + p2.substr(1);
    }
    else if (p1.back() != '/' && p2.front() != '/')
    {
        joined_path = p1 + '/' + p2;
    }
    else
    {
        joined_path = p1 + p2;
    }
    if (p2.back() != '/')
    {
        joined_path.push_back('/');
    }
    return joined_path;
}

template<typename... Args>
static std::string join_path(const std::string &p1, const std::string &p2, const Args &... args)
{
    return join_path(join_path(p1, p2), args...);
}

std::vector<std::string> traverse_folder(const std::string &path)
{
    std::vector<std::string> files;
    struct dirent *filename;    // return value for readdir()
    DIR *dir;                   // return value for opendir()
    if ((dir = opendir(path.c_str())) == nullptr)
    {
        std::cerr << "open images folder failed! program exited." << std::endl;
        exit(-1);
    }
    /* read all the files in the dir ~ */
    while ((filename = readdir(dir)) != nullptr)
    {
        // get rid of "." and ".."
        if (strcmp(filename->d_name, ".") == 0 ||
            strcmp(filename->d_name, "..") == 0)
            continue;
        // recursive search sub dirs.
        if (filename->d_type == DT_DIR)
        {
            merge_vector(files, traverse_folder(join_path(path, filename->d_name)));
            continue;
        }
        // skip hidden files and non-regular files.
        if (filename->d_name[0] == '.' || filename->d_type != DT_REG)
            continue;
        std::string file = filename->d_name;
        // get the file's extension name.
        auto file_ext = file.substr(file.rfind('.'));
        // only pick files in target extensions.
        if (std::string::npos != ext_names.find(file_ext))
            files.emplace_back(path + file);
    }
    return files;
}

void read_config(const std::string &file_path)
{
    Json::Reader reader;
    Json::Value value;
    std::ifstream f(file_path, std::ios::in);
    reader.parse(f, value, false);
    using_img = value.get("using_img", using_img).asBool();
    ext_names = value.get("ext_names", ext_names).asString();
    last_img = value.get("last_img", last_img).asInt();
    video_speed = value.get("video_speed", video_speed).asInt();
    max_find_armor_red = value.get("max_find_armor_red", max_find_armor_red).asInt();
    max_find_armor_blue = value.get("max_find_armor_blue", max_find_armor_blue).asInt();
    save_error_img_only = value.get("save_error_img_only", save_error_img_only).asBool();
}

cv::Mat read_img(const std::vector<std::string> &files, int i)
{
    if (using_img)
    {
        return cv::imread(files[i]);
    }
    else
    {
        static cv::VideoCapture cap(files[0]);
        static int now_file_cnt = 0, now_min_frame = 0, now_max_frame = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
        cv::Mat img;
        i *= video_speed;
        while (i < now_min_frame)
        {
            cap.open(files[--now_file_cnt]);
            std::cout << "\r switch to video " << now_file_cnt << ": " << files[now_file_cnt]
                      << std::endl;
            now_max_frame = now_min_frame;
            now_min_frame -= static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
        }
        while (i > now_max_frame)
        {
            if (now_file_cnt == files.size() - 1)
                break;
            cap.open(files[++now_file_cnt]);
            std::cout << "\r switch to video " << now_file_cnt << ": " << files[now_file_cnt]
                      << std::endl;
            now_min_frame = now_max_frame;
            now_max_frame += static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
        }
        cap.set(cv::CAP_PROP_POS_FRAMES, i - now_min_frame);
        cap.read(img);
        return img;
    }
}

std::size_t get_size(const std::vector<std::string> &files)
{
    if (using_img)
    {
        return files.size();
    }
    else
    {
        std::size_t max_size = 0;
        cv::VideoCapture cap;
        for (const auto &file: files)
        {
            cap.open(file);
            max_size += static_cast<std::size_t>(cap.get(cv::CAP_PROP_FRAME_COUNT));
        }
        return max_size;
    }
}
}
